
#include "gd32w51x.h"

#include "flash.h"


uint32_t flash_size(void);
void flash_read(uint32_t addr, uint8_t *bytes, uint16_t size);


static int is_valid_flash_addr(uint32_t addr)
{
    int bvalid = 0;
    if ((addr >= FLASH_BASE_ADDR) && (addr < (FLASH_BASE_ADDR + flash_size()))) {
        bvalid = 1;
    }
    return bvalid;
}

static int is_valid_ram_addr(uint32_t addr){
	if (0x20000000 < addr && addr < (0x20000000 + 0x70000)){
		return 1;
	}
	return 0;
}


static inline uint32_t flash_page_offset(uint32_t addr){
	return (uint32_t)(FLASH_PAGE_SIZE - 1) & addr;
}

static inline uint32_t flash_page_addr(uint32_t addr){
	return addr - flash_page_offset(addr);
}

static int flash_erase_need(uint32_t addr){
	uint32_t paddr = addr & ~(FLASH_PAGE_SIZE - 1);
	volatile uint32_t* pdata = (volatile uint32_t*)paddr;
	const uint32_t* pend = (uint32_t*)(paddr + FLASH_PAGE_SIZE);

	while(pdata < pend){
		if(*pdata != (uint32_t)0xFFFFFFFF){
			return 1;
		}
		pdata++;
	}

	return 0;
}

void flash_flush_icache(void){
	icache_disable();
	icache_enable();
}

static void flash_erase_page(uint32_t addr){
	/*
	关闭icache导致读取flash过慢，导致等待flash擦除超时。
	打开icache，跳过flash是否需要擦除判断，防止判断错误。
	*/
	if(flash_erase_need(addr))
	{
		fmc_flag_clear(FMC_FLAG_END);
		fmc_flag_clear(FMC_FLAG_WPERR);
		fmc_page_erase(addr);
	}
}

uint32_t flash_erase(uint32_t addr, uint32_t total)
{
	uint32_t Index;
	uint32_t PageAddress = addr;
	flash_flush_icache();
	fmc_unlock();
	for (Index = 0; Index < total; Index++) {
		flash_erase_page(PageAddress);
		PageAddress += FLASH_PAGE_SIZE;
	}
	fmc_lock();
	return 0;
}

void flash_read_with_icache(uint32_t addr, uint8_t *bytes, uint16_t size){
//		icache_enable();
	memcpy(bytes, ((const void*)addr), size);
}

void flash_read(uint32_t addr, uint8_t *bytes, uint16_t size)
{
	flash_flush_icache();
	memcpy(bytes, ((const void*)addr), size);
}

int flash_write(uint32_t addr, uint32_t *bytes, uint32_t size)
{
	fmc_state_enum fmc_state = FMC_READY;
	
	fmc_unlock();
	for (uint32_t i = 0; i < size; i++) {
		fmc_flag_clear(FMC_FLAG_END);
		fmc_flag_clear(FMC_FLAG_WPERR);
		fmc_state = fmc_word_program(addr, bytes[i]);
		
		if (fmc_state != FMC_READY)
			break;
		addr += sizeof(bytes[0]);
	}
	fmc_lock();
	
	return fmc_state;
}

typedef struct _flash_cache {
	uint32_t page_address;
	uint32_t page_offset;
	uint8_t page_buffer[FLASH_PAGE_SIZE];
}flash_cache_t;

static flash_cache_t flash_cache;

static void flash_cache_pull(flash_cache_t* cache, uint32_t page_addr){
	flash_read(page_addr, cache->page_buffer, sizeof(cache->page_buffer));
	cache->page_address = page_addr;
	cache->page_offset = 0;
}

static int flash_cache_push(flash_cache_t* cache){

	if ((cache->page_offset != 0U) && (cache->page_address != 0U)) {
		flash_write(cache->page_address, (uint32_t *) cache->page_buffer, sizeof(cache->page_buffer) / sizeof(uint32_t));
		if(!flash_verify(cache->page_address, (uint32_t *) cache->page_buffer, sizeof(cache->page_buffer))){
			return -1;
		}
	}

	return 0;
}


static int flash_write_in_page(uint32_t page, uint32_t offset, uint8_t buffer[], uint32_t length){
	flash_cache_t* cache = &flash_cache;
	uint32_t max_length = sizeof(cache->page_buffer) - offset;
	uint32_t write_length = length < max_length ? length : max_length;

	if (page != cache->page_address) {
		int ret;

		ret = flash_cache_push(cache);

		if (ret) {
			return ret;
		}

		flash_cache_pull(cache, page);
	}

	memcpy(cache->page_buffer + offset, buffer, write_length);
	cache->page_offset = offset + write_length;
	return write_length;
}


int flash_write_data(uint32_t address, uint8_t data[], uint32_t length){
	uint32_t page, offset;
	int written;
	uint8_t * pdata = data;
	uint32_t remain = length;
	uint32_t addr = address;

	while (remain > 0) {
		offset = flash_page_offset(addr);
		page = addr - offset;
		written = flash_write_in_page(page, offset, pdata, remain);
		if (written < 0) {
			return written;
		}

		pdata += written;
		remain -= written;
		addr += written;
	}

	return 0;
}

int flash_sync(void){
	flash_cache_t* cache = &flash_cache;
	return flash_cache_push(cache);
}

int flash_verify(uint32_t addr, void *bytes, uint32_t size){
	flash_flush_icache();
	int ret = memcmp((uint8_t*)addr, bytes, size) == 0;
	return ret;
}

uint32_t flash_calc_enc_sum(uint32_t addr_start, uint32_t addr_end){
	const volatile uint8_t* p = (const volatile uint8_t*)addr_start;
	const uint8_t* const pend = (uint8_t*)addr_end;
	uint32_t sum = 0;
	flash_flush_icache();
	while(p < pend){
		sum += *p ^ 0x86;
		p++;
	}
	
	return sum;
}

uint32_t flash_size(void){
	uint32_t size = *(volatile uint32_t* )0x1FFFF7E0;
	size &= 0xFFFF;
	return size * 1024;
}


